﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Diagnostics.Contracts;

namespace MemoryScannerTestConsoleApp {
    public class ActiveProcessConnection : IDisposable {

        #region Process Fields
        private Process process;
        private IntPtr? processHandle;   

        private IntPtr startingAddress;
        public IntPtr StartingAddress{
            private set {
                startingAddress = value;
            }
            get{
                if(IntPtr.Size == 4){//4 bytes, 32 bit address space
                    return new IntPtr(startingAddress.ToInt64() ); //64 bit values that can be defined in 32 bits will not throw exceptions for 32bit OSs
                }else{               //8 bytes, 64 bit address space
                    return new IntPtr( startingAddress.ToInt64() );
                }
            }
        }

        private IntPtr endingAddress;
        public IntPtr EndingAddress{
            private set {
                endingAddress = value;
            }
            get{
                if(IntPtr.Size == 4){//4 bytes, 32 bit address space
                    return new IntPtr( endingAddress.ToInt64() ); //64 bit values that can be defined in 32 bits will not throw exceptions for 32bit OSs
                }else{               //8 bytes, 64 bit address space
                    return new IntPtr( endingAddress.ToInt64() );
                }
            }
        }

        /// <summary>
        /// This determines any byte padding of the data on the stack, which sets patterned rules
        /// for where certain sized data can be placed in memory. This filters out invalid addresses
        /// which speeds up any search for particular types of data. The cost is that if the 
        /// process does not follow the alignment specified it may miss valid data. Setting this
        /// to the smallest possible packing completely removes this risk.
        /// </summary>
        public ProcessInterop.DataAlignment DataAlignment {
            get;
            set;
        }
        #endregion





        /// <summary>
        /// 
        /// </summary>
        /// <param name="friendlyProcessName"></param>
        /// <exception cref="System.ComponentModel.Win32Exception">Thrown when the processHandle could not be opened</exception>
        /// <exception cref="System.Exception">Thrown when no process friendly name matches the the string provided</exception>
        public ActiveProcessConnection( String friendlyProcessName ) {
            process = Process.GetProcessesByName( friendlyProcessName ).FirstOrDefault();
            if ( process == null ) {
                throw new Exception( "No process matches the friendly name: " + friendlyProcessName );//no matches, do error stuff
            }

            processHandle = ProcessInterop.openProcessHandle( ProcessInterop.ProcessAccessFlags.All, true, process.Id );

            StartingAddress = new IntPtr( 0x0);
            EndingAddress = new IntPtr(process.PeakVirtualMemorySize64);
            DataAlignment = ProcessInterop.DataAlignment.Byte1;
        }







        #region Data query functions





        public IntPtr[] FindBytes( byte[] value ) {
            return FindMatches( value, 1 );
        }



        /// <summary>
        /// This looks for all matches for a given number, based on a specific size of data that stores the value. It handles
        /// nuance of distinction between 2011 and 00002011 by being passed the value '2011' but told the dataUnitSize is '8'
        /// decimal digits of space.
        /// </summary>
        /// <param name="value">The value to search for</param>
        /// <param name="dataUnitSize">The size of the data that stores the value. This allows searches for 00002011 or 2011.</param>
        /// <returns>Returns all address locations that contain the specified value</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when data size is smaller than the number of bytes required to describe the value 
        /// or when the data size is larger than the maximum possible bytes to describe a value</exception>
        public IntPtr[] FindInteger( long value, uint dataUnitSize ) {  
            if ( dataUnitSize < 1 ) {
                throw new ArgumentOutOfRangeException( "data size must be larger than zero" );
            }
            byte[] byteValue = BitConverter.GetBytes( value );
            if ( dataUnitSize > byteValue.Length ) {
                throw new ArgumentOutOfRangeException( "data size must be equal or smaller than the number of bytes returned by the long" );
            }
            if(BitConverter.IsLittleEndian){
                Array.Reverse( byteValue );
            }
            System.Collections.BitArray bits = new System.Collections.BitArray(byteValue);//within single bytes, most machines treat bit order bigEndian(first address highest digit)
            int trimmedBitsLength = (int)dataUnitSize*8;
            //twos complement means negative numbers start at -1 with all bit set, and build to all zeros as their magnitude increases. 
            //Thus, negative numbers are padded with 1s instead of zeros so those 1s can be trimmed for negatives, and zeros for positives  
            bool placeholderBitValue = bits.Get(0);
            for(int i = 1; i < bits.Length - trimmedBitsLength; i ++){
                if(bits.Get(i) != placeholderBitValue){
                    throw new ArgumentOutOfRangeException( "minimum data size required to hold the value given must be less than or equal to the dataUnitSize" );
                }
            }
            bool[] trimmedBits = new bool[trimmedBitsLength];
            for ( int i = 0; i < trimmedBits.Length; i++ ) {
                trimmedBits[i] = bits.Get( i + bits.Length - trimmedBitsLength );
            }
            trimmedBits[0] = bits.Get(0);//pick up sign bit left out earlier
            byte[] trimmedBytes = new byte[dataUnitSize];
            int bitIndex = 0, byteIndex = 0;
            for (int i = 0; i < trimmedBits.Length; i++) {
                if (trimmedBits[i]) {
                    trimmedBytes[byteIndex] |= (byte)(((byte)1) << bitIndex);
                }
                bitIndex++;
                if (bitIndex == 8) {
                    bitIndex = 0;
                    byteIndex++;
                }
            }     
            if(BitConverter.IsLittleEndian){
                Array.Reverse( trimmedBytes );
            }
            //int test = BitConverter.ToInt32( trimmedBytes, 0 );
            return FindMatches( trimmedBytes, (uint)trimmedBytes.Length );
        }




        


        /// <summary>
        /// Reads an integer from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The int value representation of the bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentException">Thrown when the requested data location is not aligned properly</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public int ReadInt( uint address ) {
            return BitConverter.ToInt32( Read(address, (uint)Marshal.SizeOf(typeof(int))), 0 );
        }

        /// <summary>
        /// Reads an unsigned integer from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The uint value representation of the bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentException">Thrown when the requested data location is not aligned properly</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception> 
        public uint ReadUInt( uint address ) {
            return BitConverter.ToUInt32( Read( address, (uint)Marshal.SizeOf( typeof( uint ) ) ), 0 );
        }

        /// <summary>
        /// Reads a long integer from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The long value representation of the bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentException">Thrown when the requested data location is not aligned properly</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception> 
        public long ReadLong( uint address ) {
            return BitConverter.ToInt64( Read( address, (uint)Marshal.SizeOf( typeof( long ) ) ), 0 );
        }

        /// <summary>
        /// Reads an unsigned long integer from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The ulong value representation of the bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentException">Thrown when the requested data location is not aligned properly</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public ulong ReadULong( uint address ) {
            return BitConverter.ToUInt64( Read( address, (uint)Marshal.SizeOf( typeof( ulong ) ) ), 0 );
        }

        /// <summary>
        /// Reads a float from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The float value representation of the bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentException">Thrown when the requested data location is not aligned properly</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public float ReadFloat( uint address ) {
            return BitConverter.ToSingle( Read( address, (uint)Marshal.SizeOf( typeof( float ) ) ), 0 );
        }


        /// <summary>
        /// Reads a byte from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>A byte from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public byte ReadByte( uint address ) {
            return Read(address,1)[0];
        }


        /// <summary>
        /// Reads a byte from <b>address</b>.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public byte[] ReadBytes( uint address, uint returnArraySize ) {
            return Read( address, returnArraySize, 1 );
        }



        /// <summary>
        /// Reads a series of bytes, starting at <b>address</b>, ending with a zero-byte.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <seealso>ReadUTF8String</seealso>
        /// <seealso>ReadUTF16String</seealso>
        /// <returns></returns> 
        private byte[] ReadNullTerminatedBytes( uint address ) {
            IntPtr baseAddress = new IntPtr( address );

            if ( !isAddressInRange( baseAddress ) ) {
                throw new ArgumentOutOfRangeException("address", 
                    "the address must be in the inclusive range of " + String.Format("{0:X}", StartingAddress.ToInt64()) + ", " + String.Format("{0:X}", EndingAddress.ToInt64())); 
            }

            int returnLength = 0;
            byte[] ret = new byte[0];
            byte[] b = new byte[] { 0 };

            while ( true ) {
                ProcessInterop.readProcessMemory( processHandle.Value, baseAddress, ref b, out returnLength );
                if ( b[0] == 0 )
                    return ret;
                else {
                    Array.Resize( ref ret, ret.Length + 1 );
                    ret[ret.Length - 1] = b[0];
                    baseAddress = IntPtr.Add( baseAddress, 1 );
                }
            }
        }

        /// <summary>
        /// Reads a UTF-8 String, starting at <b>address</b>, ending with a zero-byte.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The UTF8 String representation of the null terminated bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public string ReadUTF8String( uint address ) {
            return Encoding.UTF8.GetString( ReadNullTerminatedBytes( address ) );
        }

        /// <summary>
        /// Reads a UTF-16 String, starting at <b>address</b>, ending with a zero-byte.
        /// </summary>
        /// <param name="address">Pointer to read from.</param>
        /// <returns>The UTF16 String representation of the null terminated bytes returned from the virtual memory stack</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the requested data location is not in addressible locations</exception>
        public string ReadUTF16String( uint address ) {
            return Encoding.Unicode.GetString( ReadNullTerminatedBytes( address ) );
        }



#endregion



        #region utility functions

        /// <summary>
        /// Checks all virtual memory pages within the specified memory address range
        /// for the Protection level. If any are found to be restricted, the requested
        /// region of memory cannot be read by this process.
        /// </summary>
        /// <param name="address"></param>
        /// <param name="bytesToRead"></param>
        /// <returns></returns>
        private bool isMemoryRangeReadable(IntPtr address, uint bytesToRead){
            Contract.Requires( bytesToRead > 0 );
            Contract.Requires( address.ToInt64() + bytesToRead <= EndingAddress.ToInt64() );

            long currentAddress = address.ToInt64();
            long finalAddress = address.ToInt64() + bytesToRead;

            do {
                MEMORY_BASIC_INFORMATION m  = getMBI( new IntPtr( currentAddress ) );
                if ( !isPageReadable( m ) ) {
                    return false;
                }
                currentAddress = (long)( m.BaseAddress.ToInt64() + m.RegionSize.ToInt64() );
            } while ( currentAddress <= finalAddress );

            return true;
        }


        /// <summary>
        /// Determines whether or not the current process has access to the external process 
        /// for some page in virtual memory
        /// </summary>
        /// <param name="mbi"></param>
        /// <returns></returns>
        private bool isPageReadable( MEMORY_BASIC_INFORMATION mbi ) {
            if ( mbi.Protect.HasFlag( ProcessInterop.MemoryProtection.PAGE_NOACCESS ) || 
                mbi.Protect.HasFlag( ProcessInterop.MemoryProtection.PAGE_GUARD) || 
                mbi.Protect == ProcessInterop.MemoryProtection.NO_CALLER_ACCESS ) {
                    return false;
            }
            return true;
        }


        /// <summary>
        /// Gets the MBI struct associated with the page that contains the specified address in memory
        /// </summary>
        /// <param name="address">Any address in memory within the range of the desired virtual memory page</param>
        /// <returns>An MBI struct with access levels, page size and more</returns>
        private MEMORY_BASIC_INFORMATION getMBI(IntPtr address) {
            MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION();
            ProcessInterop.virtualQueryEx( processHandle.Value, address, out mbi, (uint)Marshal.SizeOf( mbi ) );
            return mbi;
        }




        /// <summary>
        /// Compares the value of two byte[]s
        /// </summary>
        /// <param name="b1"></param>
        /// <param name="b2"></param>
        /// <returns>Returns true if all elements in both byte[]s match</returns>
        private bool isBytesEquivalent( byte[] b1, byte[] b2 ) {
            if ( b1 == null || b2 == null ) {
                return false;
            }

            if ( b1.Length != b2.Length ) {
                return false;
            }

            EqualityComparer<byte> comparer = EqualityComparer<byte>.Default;
            for ( int i = 0; i < b1.Length; i++ ) {
                if ( !comparer.Equals( b1[i], b2[i] ) ) {
                    return false;
                }
            }
            return true;

        }

        /// <summary>
        /// Tests the address for valid address within process virtual memory
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        private bool isAddressInRange(IntPtr address){
            return (address.ToInt64() >= StartingAddress.ToInt64()) && (address.ToInt64() <= EndingAddress.ToInt64());
        }


        /// <summary>
        /// Moves bytes in an existing byte[] and inserts bytes from a new byte[], like a FIFO stack
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="newBytes"></param>
        private void shiftBytes( ref byte[] bytes, byte[] newBytes ) {
            Contract.Requires( bytes.Length > 0 );
            Contract.Requires( newBytes.Length > 0 );
            Contract.Requires( bytes.Length >= newBytes.Length );

            for ( int i = 0; i < bytes.Length; i++ ) {
                if ( i < bytes.Length - newBytes.Length ) {
                    bytes[i] = bytes[i + newBytes.Length];
                } else {
                    bytes[i] = newBytes[i - bytes.Length + newBytes.Length];
                }
            }
        }


        /// <summary>
        /// Determines if the specified address is aligned correctly for a given size of data and
        /// the current byte alignment boundary
        /// </summary>
        /// <param name="address"></param>
        /// <param name="dataSize"></param>
        /// <returns></returns>
        private bool isDataAligned( IntPtr address, uint dataSize ) {
            Contract.Requires( dataSize > 0 );
            Contract.Requires( address.ToInt64() + dataSize <= EndingAddress.ToInt64() );
            int alignmentSize = (int)DataAlignment;
            if ( dataSize > alignmentSize ) {
                return ( address.ToInt64() % alignmentSize == 0 );
                //must pass through at least one boundary
            } else {
                //doesn't have to pass through a boundary so require that it does not
                return ( ( address.ToInt64() % alignmentSize ) + dataSize <= alignmentSize );     
            }
        }


        /// <summary>
        /// Reads a number of bytes from virtual memory starting from a base address.
        /// </summary>
        /// <param name="address">Base address to begin reading</param>
        /// <param name="bytesToRead">Total bytes to be read</param>
        /// <returns>A byte[] of each byte address in that memory range</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the base address or final address are outside the process range</exception>
        /// <exception cref="System.ArgumentException">Thrown when the address specified is not aligned to the data size requested and the current alignment set. 
        /// Thrown when the requested memory resides in virtual memory pages that have protection levels that restrict access to this process</exception>
        private byte[] Read( uint address, uint bytesToRead, uint dataUnitSize) {
            Contract.Requires( bytesToRead > 0 );
            Contract.Requires( dataUnitSize > 0 );
            Contract.Requires( bytesToRead >= dataUnitSize );

            IntPtr baseAddress = new IntPtr( address );

            int returnLength = 0;
            byte[] value = new byte[bytesToRead];

            if ( !isDataAligned( baseAddress, dataUnitSize ) ) {
                throw new ArgumentException( "the base address for " + dataUnitSize + " bytes is invalid for a memory alignment of " + (int)DataAlignment + " bytes", "address" );
            }

            if ( !isAddressInRange( baseAddress ) ) {
                throw new ArgumentOutOfRangeException( "address",
                    "the address must be in the inclusive range of " + String.Format( "{0:X}", StartingAddress.ToInt64() ) + ", " + String.Format( "{0:X}", EndingAddress.ToInt64() ) );
            }

            if ( !isAddressInRange( IntPtr.Add( baseAddress, value.Length - 1 ) ) ) {
                throw new ArgumentOutOfRangeException( "address",
                    "the final address of the " + bytesToRead + " vytes must be in the inclusive range of " + String.Format( "{0:X}", StartingAddress.ToInt64() ) + ", " + String.Format( "{0:X}", EndingAddress.ToInt64() ) );
            }

            if ( !isMemoryRangeReadable( baseAddress, bytesToRead ) ) {
                throw new ArgumentException( "the memory range " + baseAddress.ToInt64() + " to " + (baseAddress.ToInt64() + bytesToRead) + " contains pages with restricted access to this process. The data cannot be read." );
            }

            ProcessInterop.readProcessMemory( processHandle.Value, baseAddress, ref value, out returnLength );
            return value;
        }

        /// <summary>
        /// Overloaded. Calls Read with dataUnitSize equal to the total bytes to read
        /// </summary>
        /// <param name="address"></param>
        /// <param name="bytesToRead"></param>
        /// <returns></returns>
        private byte[] Read( uint address, uint bytesToRead ) {
            return Read( address, bytesToRead, bytesToRead );
        }



        /// <summary>
        /// Finds all matches in the entire accessible process memory space
        /// </summary>
        /// <param name="value">The byte pattern to match</param>
        /// <param name="dataUnitSize">The size of a single unit of the type of data searched for</param>
        /// <returns>An array of all base addresses that match the expected value</returns>
        private IntPtr[] FindMatches( byte[] value, uint dataUnitSize ) {
            LinkedList<IntPtr> baseLocations = new LinkedList<IntPtr>();
            //for each contiguous group of readible pages, call findmatchwithinRange on that range
            //return all the matches from all of those contiguous groups of pages
            long address = 0;

            while ( address < EndingAddress.ToInt64() ) {
                MEMORY_BASIC_INFORMATION curGroupedMBI = getMBI( new IntPtr( address ) );
                IntPtr curBaseAddress = curGroupedMBI.BaseAddress;
                long readableLength = 0;

                do {
                    if ( isPageReadable( curGroupedMBI ) ) {
                        readableLength += curGroupedMBI.RegionSize.ToInt64();
                    } else {
                        break;
                    }
                    address = curGroupedMBI.BaseAddress.ToInt64() + curGroupedMBI.RegionSize.ToInt64();
                    curGroupedMBI = getMBI( new IntPtr( address ) ); 
                } while ( address < EndingAddress.ToInt64() && isPageReadable( curGroupedMBI ) );

                if ( readableLength > 0 ) {
                    foreach ( IntPtr p in FindMatchesWithinRange( value, dataUnitSize, curBaseAddress, (uint)readableLength ) ) {
                        baseLocations.AddLast( p );
                    }
                }
                address = curGroupedMBI.BaseAddress.ToInt64() + curGroupedMBI.RegionSize.ToInt64();
            }

            return baseLocations.ToArray();
        }


        /// <summary>
        /// Looks for matches in the data. Several optimizations include:
        /// Skipping read requests if current alignment/dataSize is crossing alignment boundaries.
        /// Reading only on byte boundaries if data size >= alignment size.
        /// TODO: halt current read attempts if any initial values returned don't match
        /// (only useful when certain that it cannot match anywhere in the desired pattern)
        /// </summary>
        /// <param name="value"></param>
        /// <param name="dataUnitSize"></param>
        /// <returns></returns>
        private IntPtr[] FindMatchesWithinRange( byte[] value, uint dataUnitSize, IntPtr address, uint size ) {
            Contract.Requires( dataUnitSize <= value.Length, "Data size cannot be greater than the size of the value to look for" );
            Contract.Requires( dataUnitSize >= 1, "Data size cannot be less than 1" );
            Contract.Requires( value.Length % dataUnitSize == 0, "value length must be rationally divisible by the data unit size" );
            Contract.Requires( size >= 1, "Data range cannot be less than 1" );
            Contract.Requires( isMemoryRangeReadable( address, size ) );

            int bufferIncrement = 1;
            if ( (int)DataAlignment <= dataUnitSize ) {
                bufferIncrement = (int)DataAlignment;
            }

            LinkedList<IntPtr> baseLocations = new LinkedList<IntPtr>();
            IntPtr currentBaseLocation = new IntPtr( address.ToInt64() );
            IntPtr finalAddress = IntPtr.Add(address, (int)size -1);

            byte[] currentBytes = new byte[value.Length];
            int bytesRead = 0;

            //initial while step to populate buffer with valid data (avoids matching against zeros that don't actually exist)
            ProcessInterop.readProcessMemory( processHandle.Value, currentBaseLocation, ref currentBytes, out bytesRead );
            if ( isBytesEquivalent( value, currentBytes ) ) {
                baseLocations.AddLast( currentBaseLocation );
            }
            currentBaseLocation = IntPtr.Add( currentBaseLocation, bufferIncrement );
            int currentBytesToFill = bufferIncrement;

            while ( isAddressInRange( IntPtr.Add( currentBaseLocation, value.Length -1) ) && currentBaseLocation.ToInt64() + value.Length <= finalAddress.ToInt64() ) {
                if ( isDataAligned( currentBaseLocation, dataUnitSize ) ) {
                    byte[] tempBytes = new byte[currentBytesToFill];
                    ProcessInterop.readProcessMemory( processHandle.Value, IntPtr.Add( currentBaseLocation, value.Length -1 ), ref tempBytes, out bytesRead );//should read from curBaseLoc
                    currentBytesToFill = 0;
                    shiftBytes( ref currentBytes, tempBytes );

                    if ( this.isBytesEquivalent( value, currentBytes ) ) {
                        baseLocations.AddLast( currentBaseLocation );
                    }
                }
                currentBaseLocation = IntPtr.Add( currentBaseLocation, bufferIncrement );
                currentBytesToFill += bufferIncrement;
            }


            return baseLocations.ToArray();
        }




#endregion





        #region iDisposable

        private bool disposed = false;


        //The explicit user determined means of destroying an object
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        //The  instructions to dispose of unmanaged(and managed if not called
        //from the finalizer via the runtime GC queue but deterministically by the users).
        //Both the explicit user commands destroy the object and the default automatic
        //GC make use of this function.
        protected virtual void Dispose(bool disposing) {
            if (!disposed) {
                if (disposing) {
                    // Free other state (managed objects)
                    process.Dispose();//necessary for processes not created, but linked to?
                   
                }
                // Free your own state (unmanaged objects)
                if ( processHandle.HasValue ) {
                    ProcessInterop.closeProcessHandle( processHandle.Value );
                    processHandle = null;
                }
                disposed = true;
            }
        }

        // finalizer, used only when the garbage collector queues the object
        //for clean up and that queue action is never suppressed by an 
        //explicit call by Dispose().
        //It calls Dispose(bool) to determine how to dispose of unmanaged resources,
        //then disposes of the rest by the default behavior of the runtime GC.
        ~ActiveProcessConnection() {
            Dispose( false );
        }
#endregion


    }
}
